A* searching algorithm¶
A* is a heuristic searching algorithm that is used to find the shortest path between an initial and a final point. It is a handy algorithm that is often used for map traversal to find the shortest path to be taken. Here, we shall use this algorithm to find the shortest solution to the 8-puzzle problem
In [1]:
# we shall test our solution on these 3 puzzles
puzzle_1 = [
[1, 2, 3],
[7, 4, 5],
[8, -1, 6]
]
puzzle_2 = [
[1, 2, 3],
[4, -1, 8],
[7, 6, 5]
]
puzzle_3 = [
[1, 8, 2],
[-1, 4, 3],
[7, 6, 5]
]
Function to implement slide and get coordinates of the empty space
In [2]:
def slide(arr: list, x: int, y: int, move: str):
if(move == 'L'):
arr[y][x-1], arr[y][x] = arr[y][x], arr[y][x-1]
if(move == 'R'):
arr[y][x+1], arr[y][x] = arr[y][x], arr[y][x+1]
if(move == 'U'):
arr[y-1][x], arr[y][x] = arr[y][x], arr[y-1][x]
if(move == 'D'):
arr[y+1][x], arr[y][x] = arr[y][x], arr[y+1][x]
def getXY(arr: list, e: int) -> tuple:
n = len(arr)
x = 0
y = 0
for i in range(n):
for j in range(n):
if arr[i][j] != e: continue
x = j
y = i
break
return (x, y)
Solution class¶
In [3]:
# we shall denote SPACE by -1
class Solver:
def __init__(self, puzzle):
self.SPACE = -1
self.puzzle = [i[:] for i in puzzle]
self.LENGTH = len(puzzle)
self.solutions = []
def getCost(self,arr) -> int:
res = 0
cost = 0
for i in range(self.LENGTH):
for j in range(self.LENGTH):
if (i == self.LENGTH - 1) and (j == self.LENGTH - 1): res = self.SPACE
else: res += 1
cost += (arr[i][j] != res)
return cost
def getMinMoves(self):
x, y = getXY(self.puzzle, self.SPACE)
possible_moves = ['L', 'R', 'D', 'U']
if x == 0: possible_moves.remove('L')
if y == 0: possible_moves.remove('U')
if x == self.LENGTH - 1: possible_moves.remove('R')
if y == self.LENGTH - 1: possible_moves.remove('D')
minCost = self.LENGTH ** 2
move_cost = []
for move in possible_moves:
arr = [i[:] for i in self.puzzle]
slide(arr, x, y, move)
cost = self.getCost(arr)
move_cost.append((move, cost))
if cost < minCost: minCost = cost
move_cost = [i[0] for i in filter(lambda v:v[1] == minCost, move_cost)]
return (move_cost, x, y)
def getSoln(self, move_list = []):
if self.getCost(self.puzzle) == 0:
self.solutions.append(move_list[:])
return
res = self.getMinMoves()
for move in res[0]:
# weed out consecutive moves
if move_list != []:
temp_str = move_list[-1] + move
if (temp_str == 'LR' or
temp_str == 'RL' or
temp_str == 'UD' or
temp_str == 'DU'): continue
check_point = [i[:] for i in self.puzzle]
slide(self.puzzle, res[1], res[2], move)
self.getSoln(move_list + [move])
self.puzzle = [i[:] for i in check_point]
def solve(self):
self.getSoln()
self.solutions.sort(key = lambda x: len(x))
Driver code¶
In [4]:
if __name__ == "__main__":
s1 = Solver(puzzle_1)
s2 = Solver(puzzle_2)
s3 = Solver(puzzle_3)
s1.solve()
s2.solve()
s3.solve()
print(s1.solutions)
print(s2.solutions)
print(s3.solutions)
[['L', 'U', 'R', 'R', 'D']] [['R', 'D', 'L', 'U', 'R', 'D'], ['D', 'R', 'U', 'L', 'D', 'R']] [['R', 'U', 'R', 'D', 'D', 'L', 'U', 'R', 'D']]
Visual representation¶
In [5]:
from IPython.display import HTML, display
s1.puzzle = [i[:] for i in puzzle_1]
s2.puzzle = [i[:] for i in puzzle_2]
def cell_to_table(arr, move):
head = "<table>"
s_arr = ' '
if move == 'L': s_arr = '→';
if move == 'R': s_arr = '←';
if move == 'U': s_arr = '↓';
if move == 'D': s_arr = '↑';
for i in arr:
head += "<tr>"
for j in i:
if j == -1: head += f"<td class='ghost'>{s_arr}</td>"
else: head += f"<td>{j}</td>"
head += "</tr>"
return head + "</table>"
def getHTML(sol: Solver, problem: int) -> str:
table_html = ""
sol_no = 1
for solution in sol.solutions:
table_html += f"<h2>Solution {sol_no}</h2><div class='flexbox'>"
for move in solution:
table_html += f"{cell_to_table(sol.puzzle, move)}<span class='arr'>→</span>"
a, b = getXY(sol.puzzle, s1.SPACE)
slide(sol.puzzle, a, b, move)
table_html += cell_to_table(sol.puzzle, -1)
table_html += "</div><br/>"
sol.puzzle = [i[:] for i in puzzle_2]
sol_no += 1
return f"<h1>Problem: {problem}</h1><br/>{table_html}<hr/>"
display(HTML('''<style>
tr td{
color: white;
font-weight: bold;
text-align: center;
background-color: #1c7ed6;
}
.rendered_html tr,
.rendered_html th,
.rendered_html td{
text-align: center;
border: transparent;
font-size: 2rem;
padding: 20px;
padding-bottom: 5px;
}
.rendered_html table{
margin: 0px;
}
.ghost{
border: none;
background-color: transparent;
}
.flexbox{
display: flex;
padding: 0px;
flex-direction: row;
column-gap: 5px;
justify-content: space-between;
}
.arr{
font-size: 5rem;
height: fit-content;
margin-top: 50px;
}
</style>
''' + getHTML(s1, 1) + getHTML(s2, 2) + getHTML(s3, 3)))
Problem: 1
Solution 1
| 1 | 2 | 3 |
| 7 | 4 | 5 |
| 8 | → | 6 |
| 1 | 2 | 3 |
| 7 | 4 | 5 |
| ↓ | 8 | 6 |
| 1 | 2 | 3 |
| ← | 4 | 5 |
| 7 | 8 | 6 |
| 1 | 2 | 3 |
| 4 | ← | 5 |
| 7 | 8 | 6 |
| 1 | 2 | 3 |
| 4 | 5 | ↑ |
| 7 | 8 | 6 |
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 |
Problem: 2
Solution 1
| 1 | 2 | 3 |
| 4 | ← | 8 |
| 7 | 6 | 5 |
| 1 | 2 | 3 |
| 4 | 8 | ↑ |
| 7 | 6 | 5 |
| 1 | 2 | 3 |
| 4 | 8 | 5 |
| 7 | 6 | → |
| 1 | 2 | 3 |
| 4 | 8 | 5 |
| 7 | ↓ | 6 |
| 1 | 2 | 3 |
| 4 | ← | 5 |
| 7 | 8 | 6 |
| 1 | 2 | 3 |
| 4 | 5 | ↑ |
| 7 | 8 | 6 |
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 |
Solution 2
| 1 | 2 | 3 |
| 4 | ↑ | 8 |
| 7 | 6 | 5 |
| 1 | 2 | 3 |
| 4 | 6 | 8 |
| 7 | ← | 5 |
| 1 | 2 | 3 |
| 4 | 6 | 8 |
| 7 | 5 | ↓ |
| 1 | 2 | 3 |
| 4 | 6 | → |
| 7 | 5 | 8 |
| 1 | 2 | 3 |
| 4 | ↑ | 6 |
| 7 | 5 | 8 |
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | ← | 8 |
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 |
Problem: 3
Solution 1
| 1 | 8 | 2 |
| ← | 4 | 3 |
| 7 | 6 | 5 |
| 1 | 8 | 2 |
| 4 | ↓ | 3 |
| 7 | 6 | 5 |
| 1 | ← | 2 |
| 4 | 8 | 3 |
| 7 | 6 | 5 |
| 1 | 2 | ↑ |
| 4 | 8 | 3 |
| 7 | 6 | 5 |
| 1 | 2 | 3 |
| 4 | 8 | ↑ |
| 7 | 6 | 5 |
| 1 | 2 | 3 |
| 4 | 8 | 5 |
| 7 | 6 | → |
| 1 | 2 | 3 |
| 4 | 8 | 5 |
| 7 | ↓ | 6 |
| 1 | 2 | 3 |
| 4 | ← | 5 |
| 7 | 8 | 6 |
| 1 | 2 | 3 |
| 4 | 5 | ↑ |
| 7 | 8 | 6 |
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 |